package counter;

import java.lang.Exception;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.*;

public class Node {
    enum CStatus {
        IDLE, FIRST, SECOND, RESULT, ROOT
    };

    boolean locked;
    CStatus cStatus;
    int firstValue, secondValue;
    int result;
    Node parent;

    private final Lock lock = new ReentrantLock();
    private final Condition cond = lock.newCondition();

    public Node() {
        cStatus = CStatus.ROOT;
        locked = false;
    }

    public Node(Node myParent) {
        parent = myParent;
        cStatus = CStatus.IDLE;
        locked = false;
    }

    synchronized boolean precombine() throws Exception {
        while (locked) {
            cond.await();
        }

        switch (cStatus) {
        case IDLE:
            cStatus = CStatus.FIRST;
            return true;
        case FIRST:
            locked = true;
            cStatus = CStatus.SECOND;
            return false;
        case ROOT:
            return false;
        default:
            throw new Exception("unexpected Node state " + cStatus);
        }
    }

    synchronized int combine(int combined) throws Exception {
        while (locked) {
            cond.await();
        }
        firstValue = combined;
        switch (cStatus) {
        case FIRST:
            return firstValue;
        case SECOND:
            return firstValue + secondValue;
        default:
            throw new Exception("unexpected Node state " + cStatus);
        }

    }

    synchronized int op(int combined) throws Exception {
        switch (cStatus) {
        case ROOT:
            int prior = result;
            result += combined;
            return prior;
        case SECOND:
            secondValue = combined;
            locked = false;
            cond.notifyAll();
            while (cStatus != CStatus.RESULT) {
                cond.await();
            }
            locked = false;
            cond.notifyAll();
            cStatus = CStatus.IDLE;
            return result;
        default:
            throw new Exception("unexpected Node state " + cStatus);
        }
    }

    synchronized void distribute(int prior) throws Exception {

        switch (cStatus) {
        case FIRST:
            cStatus = CStatus.IDLE;
            locked = false;
            break;
        case SECOND:
            result = prior + firstValue;
            cStatus = CStatus.RESULT;
            break;
        default:
            throw new Exception("unexpected Node state");

        }
        cond.notifyAll();
    }

}
